/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.opentoast.maven;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.eclipse.BuildCommand;
import org.apache.maven.plugin.eclipse.EclipseSourceDir;
import org.apache.maven.plugin.eclipse.writers.EclipseClasspathWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseProjectWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseSettingsWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseWriterConfig;
import org.apache.maven.plugin.ide.IdeUtils;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.StringUtils;

/**
 * Generates the following eclipse configuration files for SpringSource dm
 * Server OSGi Bundle project:
 * <ul>
 * <li><code>.project</code></li>
 * <li><code>.classpath</code> files</li>
 * <li><code>.setting/org.eclipse.jdt.core.prefs</code> with project specific
 * compiler settings</li>
 * <li><code>.setting/org.eclipse.wst.common.project.facet.core.xml</code> for
 * SpringSource dm Server bundle facet</li>
 * </ul>
 * If this goal is run on a multi project root, dependencies between modules
 * will be configured as direct project dependencies in Eclipse (unless
 * <code>useProjectReferences</code> is set to <code>false</code>). Generates
 * eclipse configuration files for SpringSource dm Server OSGi Bundle project.
 * <p>
 * Also add M2Eclipse nature to eclipse project.
 * <p>
 * <b>Copy and modified from maven-eclipse-plugin-2.5.1</b>
 * 
 * @author <a href="mailto:jiwhiz@gmail.com">Yuan Ji</a>
 * 
 * @goal eclipse
 * @execute phase="generate-resources"
 */
public class EclipseMojo extends AbstractS2DMMojo
{
    // project natures
    protected static final String JDT_CORE_JAVA_NATURE = "org.eclipse.jdt.core.javanature";

    protected static final String WST_FACET_CORE_NATURE = "org.eclipse.wst.common.project.facet.core.nature";

    protected static final String SPRING_NATURE = "org.springframework.ide.eclipse.core.springnature";

    protected static final String S2DM_BUNDLE_NATURE = "com.springsource.server.ide.facet.core.bundlenature";

    protected static final String S2DM_PAR_NATURE = "com.springsource.server.ide.facet.core.parnature";

    protected static final String M2ECLIPSE_NATURE = "org.maven.ide.eclipse.maven2Nature";

    // build commands
    protected static final String JDT_CORE_JAVA_BUILD_COMMAND = "org.eclipse.jdt.core.javabuilder";

    protected static final String WST_FACET_BUILD_COMMAND = "org.eclipse.wst.common.project.facet.core.builder";

    protected static final String SPRING_BUILD_COMMAND = "org.springframework.ide.eclipse.core.springbuilder";

    protected static final String M2ECLIPSE_BUILD_COMMAND = "org.maven.ide.eclipse.maven2Builder";

    // container
    protected static final String COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER = "org.eclipse.jdt.launching.JRE_CONTAINER";

    protected static final String S2DM_CLASSPATH_CONTAINER = "com.springsource.server.ide.jdt.core.MANIFEST_CLASSPATH_CONTAINER";

    protected static final String M2ECLIPSE_CLASSPATH_CONTAINER = "org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER";

    /**
     * @see org.apache.maven.plugin.Mojo#execute()
     */
    public void execute() throws MojoExecutionException, MojoFailureException
    {
        getLog().debug("==>EclipseMojo.execute(), packaging="+packaging);
        
        if (MAVEN_PACKAGING_POM.equals(packaging)) //$NON-NLS-1$
        {
            getLog().warn(Messages.getString("skippompackaging"));
            return;
        }

        EclipseWriterConfig config = createEclipseWriterConfig();

        if (isS2DMParProject()) {
            getLog().debug("Set up Eclipse for S2DM Par project");
            new S2DMFacetsWriter().init(getLog(), config).write();
            new S2DMParSettingsWriter().init(getLog(), config).write();
            new EclipseProjectWriter().init(getLog(), config).write();
            ParManifestWriter writer = new ParManifestWriter();
            writer.init(getLog(), project, config.getProjectBaseDir());
            writer.write();
        } else {
            getLog().debug("Set up Eclipse for S2DM Bundle project");
            new EclipseSettingsWriter().init(getLog(), config).write();
            new S2DMFacetsWriter().init(getLog(), config).write();
            new EclipseClasspathWriter().init(getLog(), config).write();
            new EclipseProjectWriter().init(getLog(), config).write();
        }

    }

    protected EclipseWriterConfig createEclipseWriterConfig()
            throws MojoExecutionException
    {
        if (eclipseProjectDir == null) {
            eclipseProjectDir = executedProject.getFile().getParentFile();
        }

        if (!eclipseProjectDir.exists() && !eclipseProjectDir.mkdirs()) {
            throw new MojoExecutionException(Messages.getString(
                    "cantcreatedir", eclipseProjectDir)); //$NON-NLS-1$
        }

        List<String> projectnatures = new ArrayList<String>();
        projectnatures.add(WST_FACET_CORE_NATURE);
        projectnatures.add(SPRING_NATURE);
        if (isS2DMParProject()) {
            projectnatures.add(S2DM_PAR_NATURE);
        } else {
            projectnatures.add(S2DM_BUNDLE_NATURE);
            projectnatures.add(JDT_CORE_JAVA_NATURE);
            // projectnatures.add(M2ECLIPSE_NATURE);
        }

        List<BuildCommand> buildcommands = new ArrayList<BuildCommand>();
        buildcommands.add(new BuildCommand(WST_FACET_BUILD_COMMAND));
        buildcommands.add(new BuildCommand(SPRING_BUILD_COMMAND));
        if (!isS2DMParProject()) {
            buildcommands.add(new BuildCommand(JDT_CORE_JAVA_BUILD_COMMAND));
            // buildcommands.add(new BuildCommand(M2ECLIPSE_BUILD_COMMAND));
        }
        List<String> classpathContainers = new ArrayList<String>();
        classpathContainers.add(COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER);
        classpathContainers.add(S2DM_CLASSPATH_CONTAINER);
        // classpathContainers.add(M2ECLIPSE_CLASSPATH_CONTAINER);

        File projectBaseDir = executedProject.getFile().getParentFile();

        // build a list of UNIQUE source dirs (both src and resources) to be
        // used in classpath and wtpmodules
        EclipseSourceDir[] sourceDirs = buildDirectoryList(executedProject,
                eclipseProjectDir, buildOutputDirectory);

        EclipseWriterConfig config = new EclipseWriterConfig();

        config.setWorkspaceConfiguration(getWorkspaceConfiguration());

        String projectName = IdeUtils.getProjectName(
                IdeUtils.PROJECT_NAME_DEFAULT_TEMPLATE, project);

        config.setEclipseProjectName(projectName);

        config.setBuildCommands(buildcommands);

        config.setBuildOutputDirectory(buildOutputDirectory);
        config.setClasspathContainers(classpathContainers);
        config.setEclipseProjectDirectory(eclipseProjectDir);
        config.setLocalRepository(localRepository);
        config.setPde(MAVEN_PACKAGING_PAR.equals(packaging)); // ugly hack. no project
                                                // reference for par project
        config.setProject(project);
        config.setProjectBaseDir(projectBaseDir);
        config.setProjectnatures(projectnatures);
        config.setProjectFacets(null);
        config.setSourceDirs(sourceDirs);
        config.setPackaging(packaging);
        config.setDeps(doDependencyResolution());

        return config;
    }

    @SuppressWarnings("unchecked")
    private EclipseSourceDir[] buildDirectoryList(MavenProject project,
            File basedir, File buildOutputDirectory)
            throws MojoExecutionException
    {
        File projectBaseDir = project.getFile().getParentFile();

        // avoid duplicated entries
        Set<EclipseSourceDir> directories = new TreeSet<EclipseSourceDir>();
        
        extractSourceDirs(directories, (List<String>)project.getCompileSourceRoots(),
                basedir, projectBaseDir, false, null);

        String relativeOutput = IdeUtils.toRelativeAndFixSeparator(
                projectBaseDir, buildOutputDirectory, false);

        extractResourceDirs(directories, project.getBuild().getResources(),
                project, basedir, projectBaseDir, false, relativeOutput);

        // If using the standard output location, don't mix the test output into
        // it.
        String testOutput = null;
        boolean useStandardOutputDir = buildOutputDirectory.equals(new File(
                project.getBuild().getOutputDirectory()));
        if (useStandardOutputDir)
        {
            getLog().debug(
                    "testOutput toRelativeAndFixSeparator " + projectBaseDir
                            + " , "
                            + project.getBuild().getTestOutputDirectory());
            testOutput = IdeUtils.toRelativeAndFixSeparator(projectBaseDir,
                    new File(project.getBuild().getTestOutputDirectory()),
                    false);
            getLog().debug("testOutput after toRelative : " + testOutput);
        }

        extractSourceDirs(directories, project.getTestCompileSourceRoots(),
                basedir, projectBaseDir, true, testOutput);

        extractResourceDirs(directories, project.getBuild().getTestResources(),
                project, basedir, projectBaseDir, true, testOutput);

        return (EclipseSourceDir[]) directories
                .toArray(new EclipseSourceDir[directories.size()]);
    }

    private void extractSourceDirs(Set<EclipseSourceDir> directories, List<String> sourceRoots,
            File basedir, File projectBaseDir, boolean test, String output)
            throws MojoExecutionException
    {
        for (String root : sourceRoots)
        {

            File sourceRootFile = new File(root);

            if (sourceRootFile.isDirectory())
            {
                String sourceRoot = IdeUtils.toRelativeAndFixSeparator(
                        projectBaseDir, sourceRootFile, !projectBaseDir
                                .equals(basedir));

                directories.add(new EclipseSourceDir(sourceRoot, output, false,
                        test, null, null, false));
            }
        }
    }

    void extractResourceDirs(Set<EclipseSourceDir> directories, List<Resource> resources,
            MavenProject project, File basedir, File workspaceProjectBaseDir,
            boolean test, final String output) throws MojoExecutionException
    {
        for (Resource resource : resources)
        {
            getLog().debug("Processing resource dir: " + resource.getDirectory());

            String includePattern = null;
            String excludePattern = null;

            if (resource.getIncludes().size() != 0)
            {
                includePattern = StringUtils.join(resource.getIncludes()
                        .iterator(), "|");
            }

            if (resource.getExcludes().size() != 0)
            {
                excludePattern = StringUtils.join(resource.getExcludes()
                        .iterator(), "|");
            }

            // TODO: figure out how to merge if the same dir is specified twice
            // with different in/exclude patterns.

            File resourceDirectory = new File( /* basedir, */resource
                    .getDirectory());

            if (!resourceDirectory.exists() || !resourceDirectory.isDirectory())
            {
                getLog().debug(
                        "Resource dir: " + resourceDirectory
                                + " either missing or not a directory.");
                continue;
            }

            String resourceDir = IdeUtils.toRelativeAndFixSeparator(
                    workspaceProjectBaseDir, resourceDirectory,
                    !workspaceProjectBaseDir.equals(basedir));
            String thisOutput = output;
            if (thisOutput != null)
            {
                // sometimes thisOutput is already an absolute path
                File outputFile = new File(thisOutput);
                if (!outputFile.isAbsolute())
                {
                    outputFile = new File(workspaceProjectBaseDir, thisOutput);
                }
                // create output dir if it doesn't exist
                outputFile.mkdirs();

                if (!StringUtils.isEmpty(resource.getTargetPath()))
                {
                    outputFile = new File(outputFile, resource.getTargetPath());
                    // create output dir if it doesn't exist
                    outputFile.mkdirs();
                }

                getLog().debug(
                        "Making relative and fixing separator: { "
                                + workspaceProjectBaseDir + ", " + outputFile
                                + ", false }.");
                thisOutput = IdeUtils.toRelativeAndFixSeparator(
                        workspaceProjectBaseDir, outputFile, false);
            }

            getLog().debug(
                    "Adding eclipse source dir: { " + resourceDir + ", "
                            + thisOutput + ", true, " + test + ", "
                            + includePattern + ", " + excludePattern + " }.");

            directories.add(new EclipseSourceDir(resourceDir, thisOutput, true,
                    test, includePattern, excludePattern, resource
                            .isFiltering()));
        }
    }
}
